﻿/*
 * Copyright 2011 Edward L. Stauff.                  Contact: <EdStauff@gmail.com>.
 * 
 * This file, "ErrorCausedBy.cs", is part of the "Stauffware.Common" class library.
 * "Stauffware.Common" is free software: you can redistribute it and/or 
 * modify it under the terms of the GNU Lesser Public License as published by the 
 * Free Software Foundation, either version 3 of the License, or (at your option) 
 * any later version.
 * 
 * "Stauffware.Common" is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser Public License for more 
 * details.
 * 
 * You should have received a copy of the GNU Lesser Public License along with 
 * the "Stauffware.Common" sources.  If not, see <http://www.gnu.org/licenses/>.
 */

using System;

namespace Stauffware.Common.Core
{
	//#################################################################################
	/// <summary>
	/// Bag for protected stuff accessible only by ErrorCausedBy and ErrorWasCausedBy.
	/// </summary>

	public class ErrorCause_Internals
	{
		/// prevent instantiation
		protected ErrorCause_Internals () { }

		/// dictionary key for Exception.Data
		protected const string _dataKey = "ErrorCausedBy";

		/// value for Exception[_datakey]
		protected const string _user = "user";
		/// value for Exception[_datakey]
		protected const string _programmer = "programmer";
		/// value for Exception[_datakey]
		protected const string _external = "external";
	}

	//#################################################################################
	/// <summary>
	/// Use one of the three static methods in this class to tag any Exception
	/// with its root cause: the user, the programmer, or some external event.
	/// You can test this tag using methods in the ErrorWasCausedBy class.
	/// </summary>
	/// <remarks>
	/// Example #1:
	///		throw ErrorCausedBy.Programmer( new Exception("uh-oh") );
	/// 
	/// Example #2:
	///		try
	///		{
	///			myStartTime = DateTime.Parse(someUserInput);
	///		}
	///		catch (FormatException x)
	///		{
	///			ErrorCausedBy.User(x);
	///			throw;		// continue bubbling the exception up the call chain
	///		}
	/// 
	/// </remarks>

	public sealed class ErrorCausedBy : ErrorCause_Internals
	{
		/// <summary>
		/// Tags an Exception as being the fault of the end user, and returns the Exception.
		/// </summary>
		/// <param name="x">the Exception to be tagged</param>
		/// <returns>the same Exception object that was passed as a parameter</returns>
		public static Exception User (Exception x)
		{
			return Decorate(x, _user);
		}

		/// <summary>
		/// Tags an Exception as being the fault of the programmer, and returns the Exception.
		/// </summary>
		/// <param name="x">the Exception to be tagged</param>
		/// <returns>the same Exception object that was passed as a parameter</returns>
		public static Exception Programmer (Exception x)
		{
			return Decorate(x, _programmer);
		}

		/// <summary>
		/// Tags an Exception as being the fault of external conditions beyond the
		/// direct control of either the user or the programmer (e.g. disk full).
		/// </summary>
		/// <param name="x">the Exception to be tagged</param>
		/// <returns>the same Exception object that was passed as a parameter</returns>
		public static Exception External (Exception x)
		{
			return Decorate(x, _external);
		}

		private static Exception Decorate (Exception x, string culprit)
		{
			if (x.Data[_dataKey] == null)
			{
				// tag doesn't exist; create it
				x.Data[_dataKey] = culprit;
			}
			else if (x.Data[_dataKey] is string)
			{
				// tag already exists; append to it if it's different
				if (culprit != (string) x.Data[_dataKey])
					x.Data[_dataKey] = ((string) x.Data[_dataKey]) + " -> " + culprit;
			}
			//else
			//{
			//    // tag already exists, but it's not a string: 
			//    int n;
			//    for (n = 1; x.Data[_dataKey + n.ToString()] != null; n++)
			//        ;
			//    x.Data[_dataKey + n.ToString()] = culprit;
			//}
			return x;
		}
	}

	//#################################################################################
	/// <summary>
	/// Provides static methods for testing the tags set by the methods in the ErrorCausedBy class.
	/// </summary>

	public sealed class ErrorWasCausedBy : ErrorCause_Internals
	{
		/// <summary>
		/// Returns true if the given Exception was tagged by ErrorCausedBy.User
		/// (and not ErrorCausedBy.Programmer or ErrorCausedBy.External as well).
		/// </summary>
		/// <param name="x">the exception to check</param>
		/// <returns></returns>
		public static bool User (Exception x)
		{
			return CheckFor(x, _user);
		}

		/// <summary>
		/// Returns true if the given Exception was tagged by ErrorCausedBy.Programmer
		/// (and not ErrorCausedBy.User or ErrorCausedBy.External as well).
		/// </summary>
		/// <param name="x">the exception to check</param>
		/// <returns></returns>
		public static bool Programmer (Exception x)
		{
			return CheckFor(x, _programmer);
		}

		/// <summary>
		/// Returns true if the given Exception was tagged by ErrorCausedBy.External
		/// (and not ErrorCausedBy.User or ErrorCausedBy.Programmer as well).
		/// </summary>
		/// <param name="x">the exception to check</param>
		/// <returns></returns>
		public static bool External (Exception x)
		{
			return CheckFor(x, _external);
		}

		private static bool CheckFor (Exception x, string culprit)
		{
			string tag = x.Data[_dataKey] as string;
			return (culprit == tag);
		}
	}

	//#################################################################################
}
